/*
    Copyright © 2010  Ahti Legonkov
    
    This file is part of j+-.
    
    j+- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    j+- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package cx.ath.skyflyer.javapm.visitor;

import cx.ath.skyflyer.javapm.symbol.SymbolTable;
import cx.ath.skyflyer.javapm.util.MessageCollection;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.Node;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.ConstructorDeclaration;
import japa.parser.ast.body.DestructorDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.QualifiedNameExpr;
import japa.parser.ast.stmt.ForStmt;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.visitor.VoidVisitorAdapter;

/**
 * Checks some syntax errors.
 * @author Ahti Legonkov
 */
public class SyntaxCheckVisitor extends VoidVisitorAdapter {

    private final MessageCollection messages = new MessageCollection();
    private ClassOrInterfaceDeclaration currentClass = null;

    public MessageCollection getMessages() {
        return messages;
    }

    @Override
    public void visit(ClassOrInterfaceDeclaration n, Object arg) {
        final ClassOrInterfaceDeclaration prevClass = currentClass;
        currentClass = n;
        super.visit(n, arg);
        currentClass = prevClass;
    }

    @Override
    public void visit(FieldDeclaration n, Object arg) {
        final int modifiers = n.getModifiers();

        if (ModifierSet.isReadOnly(modifiers)
                && n.getType() instanceof PrimitiveType) {
            messages.addWarning1(
                    "'readonly' on primitive types behaves the same as if it were 'final'.", "",
                    n.getBeginLine(), n.getBeginColumn());
        }
        super.visit(n, arg);
    }

    @Override
    public void visit(ForStmt n, Object arg) {

        // TODO: check that none of the inits try to declare a readonly
        super.visit(n, arg);
    }

    @Override
    public void visit(MethodDeclaration n, Object arg) {
        final int modifiers = n.getModifiers();
        if (ModifierSet.isReadOnly(modifiers)
                && ModifierSet.isStatic(modifiers)) {
            messages.addError(
                    "'static' method cannot be 'readonly'.", "",
                    n.getBeginLine(), n.getBeginColumn());

            return;
        }

        super.visit(n, arg);
    }

    private boolean nameEqualsClassName(String name, boolean isDestructor, Node n, SymbolTable st) {
        final String entity = isDestructor
                ? "Destructor"
                : "Constructor";

        if (currentClass == null) {
            messages.addError(
                    entity+" must be declared inside a class", "",
                    n.getBeginLine(), n.getBeginColumn());

            return false;
        }

        if (currentClass.isInterface() && isDestructor) {
            messages.addError(
                    "Destructors cannot appear in interfaces", "",
                    n.getBeginLine(), n.getBeginColumn());

            return false;
        }

        if (!currentClass.getName().equals(name)) {
            messages.addError(
                    entity + " must have same name as the class it's in", "",
                    n.getBeginLine(), n.getBeginColumn());

            return false;
        }

        return true;
    }

    @Override
    public void visit(ConstructorDeclaration n, Object arg) {
        assert(n.getData() != null && n.getData() instanceof SymbolTable);
        final SymbolTable symbolTable = (SymbolTable)n.getData();

        if (nameEqualsClassName(n.getName(), false, n, symbolTable)) {
            super.visit(n, arg);
        }
    }

    @Override
    public void visit(DestructorDeclaration n, Object arg) {
        // Make sure that the SymbolTableCreator has run.
        assert(n.getData() != null);
        assert(n.getData() instanceof SymbolTable);

        if (ModifierSet.isAuto(n.getModifiers())) {
            messages.addError(
                    "Destructor cannot be declared 'auto'", "",
                    n.getBeginLine(), n.getBeginColumn());
            return;
        }

        if (ModifierSet.isStatic(n.getModifiers())) {
            messages.addError(
                    "Destructor cannot be declared 'static'", "",
                    n.getBeginLine(), n.getBeginColumn());
            return;
        }

        if (!nameEqualsClassName(n.getName(), true, n, (SymbolTable)n.getData()))
            return;

        super.visit(n, arg);
    }

    @Override
    public void visit(ImportDeclaration n, Object arg) {
        if (n.isAsterisk()) {
            messages.addError("type-import-on-demand not supported.", "",
                    n.getBeginLine(), n.getBeginColumn());
            return;
        }
        
        super.visit(n, arg);
    }
}
